#include "parse.h"
#include "la.h"
#include "error.h"
#include "util.h"
#include "table.h"
#include "midcode.h"

void program();
void const_decl(TokenType tt, char *tb);
void var_decl(TokenType tt, char *tb);
void func_ret_decl(TokenType tt, char *tb);
void func_reg_decl(char *tb);
void func_main_decl();

void param_list();
void compound_stmt();
void stmt_list(TokenType tt, char *tb);
void statement(TokenType tt, char *tb, Bool *flag);
void if_stmt();
void while_stmt();
void for_stmt();

Operand func_ret_call(FuncElement *fe);
void func_reg_call(FuncElement *fe);
void assign_stmt(NormalElement *ne);
void scanf_stmt();
void printf_stmt();
void return_stmt();

void condition(Opcode *oc, Operand or[/*2*/]);
Operand expression();
Operand term();
Operand factor();

static Class p_clss;
static Type p_type;
static Value p_value;
static int p_dcl_row;
//static TokenType p_param_list[MAX_PARAM_NUM];

void set_param_for_table(TokenType tt, char *tb, Class clss) {
    if (clss == C_VAR) {
        p_clss = (current_scope == NULL) ? GLOBAL_VAR : LOCAL_VAR;
    } else if (clss == C_CONST) {
        p_clss = (current_scope == NULL) ? GLOBAL_CONST : LOCAL_CONST;
    } else {
        p_clss = clss;
    }

    if (tb != NULL) {
        p_value = str_to_value(tt, tb);
    }

    p_type = tt2tp(tt);
    p_dcl_row = row;
}

void parse()
{
    midcode_init();
    program();
    set_last_map();
    table_ref_check();
}
void program() {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];

    do {
        read_token();
        // The first CONST token.
        if (current_token == CONST) {
            read_token();
            if (isTYPE_DECL(current_token) == TRUE) {
                t_tt = current_token;
                read_token();
                if (current_token == IDENT) {
                    strcpy(t_tb, token_buf);
                    read_token();
                    if (current_token != ASSIGN) {
                        print_error("Compiler is expecting an ASSIGN token.", token_buf, ",");
                        continue;
                    }
                    const_decl(t_tt, t_tb);
                } else {
                    print_error("Compiler is expecting an IDENT.", token_buf,
                        ";");
                    continue;
                }
            } else {
                print_error("Identifier's type must be INT, FLOAT, or CHAR.", token_buf, ";");
                continue;
            }
        } else if (isTYPE_DECL(current_token) == TRUE) {
            t_tt = current_token;
            read_token();
            if (current_token == IDENT) {
                strcpy(t_tb, token_buf);
                read_token();
                switch (current_token) {
                    case ASSIGN:
                        const_decl(t_tt, t_tb);
                        break;
                    case COMMA:
                    case SEMICOLON:
                        put_back();
                        var_decl(t_tt, t_tb);
                        break;
                    case LPAREN:
                        put_back();
                        func_ret_decl(t_tt, t_tb);
                        break;
                    default:
                        print_error("Unexpected character.", "Expecting a \'=\', \',\', or \'(\'.", ";");
                        continue;
                }
            } else {
                print_error("Compiler is expecting an IDENT.", token_buf, ";");
                continue;
            }
        }
        // function with no return value, main function
        else if (current_token == VOID) {
            read_token();
            switch (current_token) {
                case IDENT:
                    func_reg_decl(token_buf);
                    break;
                case MAIN:
                    func_main_decl();
                    return;
                default:
                    print_error("Unrecognized declaration.", "Expecting IDENT or MAIN.", ";");
                    continue;
            }
        } else {
            print_error("Unrecognized declaration.", "Expecting CONST, TYPE_DECL, or VOID.", ";");
            continue;
        }
    } while (!feof(source));
}

void const_decl(TokenType tt, char *tb) {
    TokenType t_tt = tt;
    char t_tb[MAX_TOKEN_LEN];
    char t_tb_value[MAX_TOKEN_LEN];
	int neg = 0;

    read_token();
	if (current_token == MINUS) {
        t_tb_value[neg++] = '-';
		read_token();
	}
    if (!token_type_match(t_tt, current_token)) {
        print_error("Compiler is expecting a valid constant value for the const declaration", token_buf, ",;");
    } else {
        strcpy(&t_tb_value[neg], token_buf);
        set_param_for_table(t_tt, t_tb_value, C_CONST);
        enter_normal_element(tb, p_clss, p_type, p_value, p_dcl_row);
    }
    do {
        read_token();
        if (current_token != COMMA) {
            break;
        }
        // IDENT
        read_token();
        if (current_token != IDENT) {
            print_error("Compiler is expecting an IDENT.", token_buf, ",;");
            continue;
        }
        strcpy(t_tb, token_buf);
        // ASSIGN
        read_token();
        if (current_token != ASSIGN) {
            print_error("Compiler is expecting an ASSIGN within const declaration.", token_buf, ",");
            continue;
        }
        // VALUE
        neg = 0;
        read_token();
        if (current_token == MINUS) {
            t_tb_value[neg++] = '-';
            read_token();
        }
        if (!token_type_match(t_tt, current_token)) {
            print_error("Compiler is expecting a valid constant value for the const declaration", token_buf, ",");
            continue;
        }
        else {
            strcpy(&t_tb_value[neg], token_buf);
        }
        // Enter element.
        set_param_for_table(t_tt, t_tb_value, C_CONST);
        enter_normal_element(t_tb, p_clss, p_type, p_value, p_dcl_row);
    } while (TRUE);
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", "", NULL);
        put_back();
    }
}

void var_decl(TokenType tt, char *tb) {
    //TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];
    set_param_for_table(tt, NULL, C_VAR);
    enter_normal_element(tb, p_clss, p_type, p_value, p_dcl_row);

    do {
        read_token();
        if (current_token != COMMA) {
            break;
        }
        // IDENT
        read_token();
        if (current_token != IDENT) {
            print_error("Compiler is expecting an IDENT.", token_buf, ",");
            continue;
        }
        // Enter element.
        set_param_for_table(tt, NULL, C_VAR);
        enter_normal_element(token_buf, p_clss, p_type, p_value, p_dcl_row);
    } while (TRUE);
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", "", ";");
    }
}

void func_ret_decl(TokenType tt, char *tb) {
    //TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    Operand op1;

    strcpy(t_tb, tb);

    // scope change
    update_scope();
    // reset temp_p
    reset_temp_p();
    // Parameter list.
    param_list();
    // Enter element.
    set_param_for_table(tt, NULL, FUNC_RET);
    enter_func_element(t_tb, p_clss, p_type, /*p_param_list, */p_dcl_row);
    // Enter midcode.
    set_operand(&op1, &func_table[func_table_p - 1], FUNC_CONT);
    enter_midcode(midcode_table_p, FUNC_OP, op1, fallback, fallback);
    // Function body
    compound_stmt();
}

void func_reg_decl(char *tb) {
    //TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    Operand op1;

    strcpy(t_tb, tb);
    // scope change
    update_scope();
    // reset temp_p
    reset_temp_p();
    // Parameter list.
    param_list();
    set_param_for_table(VOID, NULL, FUNC_REG);
    enter_func_element(t_tb, p_clss, p_type, /*p_param_list, */p_dcl_row);
    // Enter midcode.
    set_operand(&op1, &func_table[func_table_p - 1], FUNC_CONT);
    enter_midcode(midcode_table_p, FUNC_OP, op1, fallback, fallback);
    // Function body
    compound_stmt();
    enter_midcode(midcode_table_p, RETN_OP, fallback, fallback, fallback);
}

void func_main_decl() {
    //TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];
    Operand op1;

    // scope change
    update_scope();
    // reset temp_p
    reset_temp_p();
    // Parameter list.
    /*memset(p_param_list, 0, sizeof(p_param_list));*/
    enter_func_element("MAIN", FUNC_REG, T_VOID, row);
    // Enter midcode.
    set_operand(&op1, &func_table[func_table_p - 1], FUNC_CONT);
    enter_midcode(midcode_table_p, FUNC_OP, op1, fallback, fallback);
    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting a LPAREN.", token_buf, ")");
        return;
    }
    // RPAREN
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting a RPAREN.", token_buf, ";");
        return;
    }
    compound_stmt();
}

void param_list() {
    TokenType t_tt;

    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting an LPAREN.", "", ",)");
    }
    // Param list
    do {
        // TYPE_DECL
        read_token();
        if (current_token == RPAREN) {
            break;
        }
        else if (!isTYPE_DECL(current_token)) {
            print_error("Identifier's type must be INT, FLOAT, or CHAR.", "", ",)");
            continue;
        }
        t_tt = current_token;
        // IDENT
        read_token();
        if (current_token != IDENT) {
            print_error("Compiler is expecting an IDENT.", token_buf, ",)");
            continue;
        }
        // Enter element.
        set_param_for_table(t_tt, NULL, PARAMETER);
        enter_normal_element(token_buf, p_clss, p_type, p_value, p_dcl_row);
        enter_func_param(tt2tp(t_tt));
        // COMMA
        read_token();
        if (current_token != COMMA) {
            break;
        }
    } while (TRUE);
    // RPAREN
    if (current_token != RPAREN) {
        print_error("Compiler is expecting an RPAREN.", "", "{");
    }
}

void compound_stmt() {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    // L_CURLY_BRACE
    read_token();
    if (current_token != L_CURLY_BRACE) {
        print_error("Compiler is expecting an L_CURLY_BRACE.", "", ";");
    }
    // Constant and variable declaration.
    do {
        read_token();
        // The first CONST token.
        if (current_token == CONST) {
            read_token();
            if (isTYPE_DECL(current_token) == TRUE) {
                t_tt = current_token;
                read_token();
                if (current_token == IDENT) {
                    strcpy(t_tb, token_buf);
                    read_token();
                    if (current_token != ASSIGN) {
                        print_error("Compiler is expecting an ASSIGN token.", token_buf, ",");
                        continue;
                    }                    
                    const_decl(t_tt, t_tb);
                } else {
                    print_error("Compiler is expecting an IDENT.", token_buf, ";");
                    continue;
                }
            } else {
                print_error("Identifier's type must be INT, FLOAT, or CHAR.", token_buf, ";");
                move_forward();
                continue;
            }
        } else if (isTYPE_DECL(current_token) == TRUE) {
            t_tt = current_token;
            read_token();
            if (current_token == IDENT) {
                strcpy(t_tb, token_buf);
                read_token();
                switch (current_token) {
                    case ASSIGN:
                        const_decl(t_tt, t_tb);
                        break;
                    case COMMA:
                        put_back();
                        var_decl(t_tt, t_tb);
                        break;
                    case SEMICOLON:
                        put_back();
                        var_decl(t_tt, t_tb);
                        break;
                    default:
                        print_error("Unexpected character.", "Expecting a \'=\', \',\', or \'(\'.", ";");
                        continue;
                }
            } else {
                print_error("Compiler is expecting an IDENT.", token_buf, ";");
                continue;
            }
        } else {
            break;
        }
    } while (TRUE);
    // statement
    t_tt = current_token;
    strcpy(t_tb, token_buf);
    stmt_list(t_tt, t_tb);
    read_token();
    // R_CURLY_BRACE
    if (current_token != R_CURLY_BRACE) {
        print_error("Compiler is expecting an R_CURLY_BRACE.", "", ";");
    }
}

void stmt_list(TokenType tt, char *tb) {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    Bool first_in = TRUE;
    Bool flag = TRUE;

    t_tt = tt;
    strcpy(t_tb, tb);
    while (flag) {
        if (t_tt == L_CURLY_BRACE && first_in == TRUE) {
            read_token();
            t_tt = current_token;
            strcpy(t_tb, token_buf);
            first_in = FALSE;
        }
        statement(t_tt, t_tb, &flag);
        if (flag == TRUE) {
            read_token();
            t_tt = current_token;
            strcpy(t_tb, token_buf);
        }
    }
}

void statement(TokenType tt, char *tb, Bool *flag) {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    FuncElement *t_fe;
    NormalElement *t_ne;

    t_tt = tt;
    strcpy(t_tb, tb);
    switch (t_tt) {
        case IF:
            if_stmt();
            break;
        case WHILE:
            while_stmt();
            break;
        case FOR:
            for_stmt();
            break;
        case L_CURLY_BRACE:
            stmt_list(L_CURLY_BRACE, t_tb);
            read_token();
            if (current_token != R_CURLY_BRACE) {
                print_error("Compiler is expecting an R_CURLY_BRACE token.", token_buf, "");
            }
            break;
        case IDENT:
            // is function?
            t_fe = func_table_lookup(t_tb);
            if (t_fe != NULL) {
                if (t_fe->type == T_VOID) {
                    func_reg_call(t_fe);
                } else {
                    func_ret_call(t_fe);
                }
                // SEMICOLON
                read_token();
                if (current_token != SEMICOLON) {
                    print_error("Compiler is expecting an SEMICOLON token.", token_buf, "");
                }
                break;
            }
            // is variable
            t_ne = normal_table_lookup(t_tb);
            if (t_ne != NULL) {
                if (is_constant(t_ne->clss) == FALSE) {
                    assign_stmt(t_ne);
                } else {
                    print_error("Identifier is not supposed to be constant at the left side of an assignment.", t_tb, ";");
                    *flag = FALSE;
                    return;
                }
                break;
            }
            print_error("Unrecognized identifier, it is neither a function nor a variable.", t_tb, ";");
            *flag = FALSE;
            return;
        case SCANF:
            scanf_stmt();
            break;
        case PRINTF:
            printf_stmt();
            break;
        case RETURN:
            return_stmt();
            break;
        case R_CURLY_BRACE:
            put_back();
            *flag = FALSE;
            break;
        // if statement
        case ELSE:
            *flag = FALSE;
            break;
        case SEMICOLON:
            break;
        default:
            *flag = FALSE;
    }
}

void if_stmt() {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    int position[2] = {0};
    Opcode t_oc;
    Operand t_or[3];
    Bool flag = TRUE;
    Bool ever_in_else = FALSE;

    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting an LPAREN.", token_buf, ");");
    } else {
        condition(&t_oc, t_or);
        // Preserve position.
        position[0] = midcode_table_p;
        mid_move_forward(2);
    }
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting an RPAREN.", token_buf, ";");
    }
    // STATEMENT
    read_token();
    t_tt = current_token;
    strcpy(t_tb, token_buf);
    // Insert midcode
    set_operand(&t_or[2], &midcode_table_p, JUMP_CONT);
    enter_midcode(position[0], t_oc, t_or[0], t_or[1], t_or[2]);
    statement(t_tt, t_tb, &flag);
    // ELSE
    read_token();
    if (current_token == ELSE) {
        // Found ELSE token.
        ever_in_else = TRUE;
        // Preserve position.
        position[1] = midcode_table_p;
        mid_move_forward(1);

        read_token();
        t_tt = current_token;
        strcpy(t_tb, token_buf);
        // Insert midcode.
        set_operand(&t_or[0], &midcode_table_p, JUMP_CONT);
        enter_midcode(position[0] + 1, JMP_OP, t_or[0], fallback, fallback);
        statement(t_tt, t_tb, &flag);
        // Insert midcode.
        set_operand(&t_or[0], &midcode_table_p, JUMP_CONT);
        enter_midcode(position[1], JMP_OP, t_or[0], fallback, fallback);
    }
    else {
        put_back();
    }
    if (ever_in_else == FALSE) {
        set_operand(&t_or[0], &midcode_table_p, JUMP_CONT);
        enter_midcode(position[0] + 1, JMP_OP, t_or[0], fallback, fallback);
    }
}

void while_stmt() {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    Opcode t_oc;
    Operand t_or[3];
    int position[2];

    Bool flag = TRUE;
    
    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting an LPAREN.", token_buf, ")");
        return;
    }
    // condition
    position[0] = midcode_table_p;
    condition(&t_oc, t_or);
    // Preserve position.
    position[1] = midcode_table_p;
    mid_move_forward(2);
    // Enter midcode.
    set_operand(&t_or[2], &midcode_table_p, JUMP_CONT);
    enter_midcode(position[1], t_oc, t_or[0], t_or[1], t_or[2]);
    // RPAREN
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting an RPAREN.", "condition", "{");
    }
    // statement
    read_token();
    t_tt = current_token;
    strcpy(t_tb, token_buf);
    statement(t_tt, t_tb, &flag);
    // Enter midcode.
    set_operand(&t_or[0], &position[0], JUMP_CONT);
    enter_midcode(midcode_table_p, JMP_OP, t_or[0], fallback, fallback);

    set_operand(&t_or[0], &midcode_table_p, JUMP_CONT);
    enter_midcode(position[1] + 1, JMP_OP, t_or[0], fallback, fallback);
}

void for_stmt() {
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    //TokenType t_tt_b;
    //char t_tb_b[MAX_TOKEN_LEN];
    //FuncElement *t_fe;
    NormalElement *t_ne;
    //NormalElement *t_ne_b;
    Opcode t_oc[2];
    Operand t_or[7];
    int position[2];
    Bool flag = TRUE;
    int step = 0;

    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting an LPAREN.", token_buf, "()");
    }
    // IDENTIFIER = EXPRESSION
    read_token();
    if (current_token == IDENT) {
        t_ne = normal_table_lookup(token_buf);
        if (t_ne == NULL || is_constant(t_ne->clss)) {
            print_error("Can't find the specified identifier in current scope.", token_buf, ";");
        }
        else {
            set_operand(&t_or[0], t_ne, NORMAL_CONT);
            read_token();
            if (current_token != ASSIGN) {
                print_error("Compiler is expecting an ASSIGN token.", token_buf, ";");
            }
            t_or[1] = expression();
            enter_midcode(midcode_table_p, ASSIGN_OP, t_or[0], t_or[1], fallback);
        }
    }
    read_token();
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", token_buf, ";");
    }
    // condition
    position[0] = midcode_table_p;
    condition(&t_oc[0], t_or);
    // Preserve position.
    position[1] = midcode_table_p;
    mid_move_forward(2);
    read_token();
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", token_buf, ";");
    }
    // IDENTIFIER = IDENTIFIER + STEP
    read_token();
    if (current_token == IDENT) {
        t_ne = normal_table_lookup(token_buf);
        if (t_ne == NULL || is_constant(t_ne->clss)) {
            print_error("Can't find the specified identifier in current scope.", token_buf, ";");
        }
        else {
            set_operand(&t_or[3], t_ne, NORMAL_CONT);
            // ASSIGN
            read_token();
            if (current_token != ASSIGN) {
                print_error("Compiler is expecting an ASSIGN token.", token_buf, ";");
            }
            read_token();
            if (current_token == IDENT) {
                t_ne = normal_table_lookup(token_buf);
                if (t_ne == NULL || is_constant(t_ne->clss)) {
                    print_error("Can't find the specified identifier in current scope.", token_buf, "){;");
                }
                else {
                    set_operand(&t_or[4], t_ne, NORMAL_CONT);
                }
            }
            // ADDITIVE_OP
            read_token();
            if (isADDITIVE_OP(current_token) == TRUE) {
                t_oc[1] = tt2oc(current_token);
                // STEP
                read_token();
                if (current_token == C_INT) {
                    step = str_to_value(C_INT, token_buf).int_val;
                    set_const_operand(&t_or[5], &step, C_INT);
                }
                else {
                    print_error("Compiler is expecting an C_INT.", token_buf, ");");
                }
            }
            else {
                print_error("Compiler is expecting an ADDITIVE_OP.", token_buf, ")");
            }
        }
    }
    else {
        print_error("Compiler is expecting an IDENT.", token_buf, ")");
    }
    // RPAREN
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting a RPAREN.", token_buf, ";");
    }
    // statement
    read_token();
    t_tt = current_token;
    strcpy(t_tb, token_buf);
    // Insert midcode: TO STATEMENT
    set_operand(&t_or[2], &midcode_table_p, JUMP_CONT);
    enter_midcode(position[1], t_oc[0], t_or[0], t_or[1], t_or[2]);

    statement(t_tt, t_tb, &flag);

    // IDENTIFIER: + STEP
    set_operand(&t_or[6], enter_temp_element(), NORMAL_CONT);
    enter_midcode(midcode_table_p, t_oc[1], t_or[4], t_or[5], t_or[6]);
    // IDENTIFIER: =
    enter_midcode(midcode_table_p, ASSIGN_OP, t_or[3], t_or[6], fallback);
    // Jump back
    set_operand(&t_or[0], &position[0], JUMP_CONT);
    enter_midcode(midcode_table_p, JMP_OP, t_or[0], fallback, fallback);
    // Condition wasn't fulfiled
    set_operand(&t_or[2], &midcode_table_p, JUMP_CONT);
    enter_midcode(position[1] + 1, JMP_OP, t_or[2], fallback, fallback);
}

void func_reg_call(FuncElement *fe) {
    //TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];
    Operand op1;
    Operand op3;
    int param_count = 0;
	Bool first_in = TRUE;

    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting a LPAREN.", token_buf, ",");
    }
    // PARAMETERS. Type Checking is needed.
    do {
		if (first_in == TRUE) {
			read_token();
			if (current_token == RPAREN) {
				break;
			}
			else {
				put_back();
				first_in = FALSE;
			}
		}
        op1 = expression();
        enter_midcode(midcode_table_p, PARA_OP, op1, fallback, fallback);
        param_count++;
        read_token();
    } while (current_token == COMMA);
    put_back();
    if (param_count != fe->param_num) {
        print_error("Parameter number doesn't match.", fe->name, "");
    }
    op1.operandType = FUNC_MB;
    op1.operandCont.funcOp = fe;
    op3.operandType = TEMP_VAR;
    op3.operandCont.varOp = enter_temp_element();
    enter_midcode(midcode_table_p, CALL_OP, op1, fallback, op3);
    // RPAREN
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting a RPAREN.", token_buf, ";");
    }
}

Operand func_ret_call(FuncElement *fe) {
    //TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];
    Operand op1;
    Operand op3;
    int param_count = 0;
	Bool first_in = TRUE;

    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting a LPAREN.", token_buf, ",");
    }
    // PARAMETERS. Type Checking is needed.
    do {
		if (first_in == TRUE) {
			read_token();
			if (current_token == RPAREN) {
				break;
			}
			else {
				put_back();
				first_in = FALSE;
			}
		}
        op1 = expression();
        enter_midcode(midcode_table_p, PARA_OP, op1, fallback, fallback);
        param_count++;
        read_token();
    } while (current_token == COMMA);
    put_back();
    if (param_count != fe->param_num) {
        print_error("Parameter number doesn't match.", fe->name, "");
    }
    op1.operandType = FUNC_MB;
    op1.operandCont.funcOp = fe;
    set_operand(&op3, enter_temp_element(), NORMAL_CONT);
    enter_midcode(midcode_table_p, CALL_OP, op1, op3, fallback);
    // RPAREN
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting a RPAREN.", token_buf, ";");
    }

    return op3;
}

void assign_stmt(NormalElement *ne) {
    //TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];
    Operand op1;
    Operand op2;
    set_operand(&op1, ne, NORMAL_CONT);
    // ASSIGN
    read_token();
    if (current_token != ASSIGN) {
        print_error("Compiler is expecting an ASSIGN token.", token_buf, ";");
    }
    else {
        // expression
        op2 = expression();
        enter_midcode(midcode_table_p, ASSIGN_OP, op1, op2, fallback);
    }
    // SEMICOLON
    read_token();
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", token_buf, "");
    }
}

void scanf_stmt() {
    //TokenType t_tt;
    NormalElement *t_ne;
    char t_tb[MAX_TOKEN_LEN];
    Operand op1;

    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting a LPAREN.", token_buf, ",");
    }
    do {
        read_token();
        if (current_token != IDENT) {
            print_error("Compiler is expecting an IDENT.", token_buf, ",;");
            continue;
        }
        else {
            // is variable
            strcpy(t_tb, token_buf);
            t_ne = normal_table_lookup(t_tb);
            if (t_ne != NULL) {
                if (is_constant(t_ne->clss) != TRUE) {
                    if (t_ne->scope == current_scope || t_ne->scope == NULL) {
                        set_operand(&op1, t_ne, NORMAL_CONT);
                        enter_midcode(midcode_table_p, SCANF_OP, op1, fallback, fallback);
                    }
                    else {
                        print_error("Scope unmatch.", t_tb, ";");
                        return;
                    }
                } else {
                    print_error("Constant type can't be a parameter of scanf function.", t_tb, ";");
                    return;
                }
            }
            else {
                print_error("Can't find the specified identifier in current scope.", t_tb, ";");
                return;
            }
            // COMMA
            read_token();
            if (current_token != COMMA) {
                break;
            }
        }
    } while (TRUE);
    // RPAREN
    if (current_token != RPAREN) {
        print_error("Compiler is expecting a RPAREN.", token_buf, ";");
        return;
    }
    // SEMICOLON
    read_token();
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", token_buf, "");
    }
}

void printf_stmt() {
    //TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    Operand op1;
    Bool in_str = FALSE;
    // LPAREN
    read_token();
    if (current_token != LPAREN) {
        print_error("Compiler is expecting a LPAREN.", token_buf, ",;");
    }
    // If it's not a expression, after reading a token the function expression() will quit.
    op1 = expression();
    if (current_token == C_STR) {
        in_str = TRUE;
        read_token();
        strcpy(t_tb, token_buf);
        // do something to print a constant string.
        set_operand(&op1, t_tb, STRING_CONT);
        enter_midcode(midcode_table_p, PRINTF_OP, op1, fallback, fallback);
        read_token();
        switch (current_token) {
            case COMMA:
                op1 = expression();
                enter_midcode(midcode_table_p, PRINTF_OP, op1, fallback, fallback);
                break;
            case RPAREN:
                put_back();
                break;
            default:
                print_error("Unexpected character.", token_buf, ";");
                move_forward();
                return;
        }
    }
    if (in_str == FALSE) {
        enter_midcode(midcode_table_p, PRINTF_OP, op1, fallback, fallback);
    }
    // RPAREN
    read_token();
    if (current_token != RPAREN) {
        print_error("Compiler is expecting a RPAREN.", token_buf, ";");
    }
    // SEMICOLON
    read_token();
    if (current_token != SEMICOLON) {
        print_error("Compiler is expecting an SEMICOLON token.", token_buf, "");
    }
}

// changes may be needed.
void return_stmt() {
    //TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];
    Operand op1;

    read_token();
    if (current_token == SEMICOLON) {
        if (current_scope->type != T_VOID) {
            print_error("Function must return a value.", current_scope->name, "");
        }
        else {
            enter_midcode(midcode_table_p, RETN_OP, fallback, fallback, fallback);
        }
    }
    else if (current_token == LPAREN) {
        // expression
        op1 = expression();
        enter_midcode(midcode_table_p, RETV_OP, op1, fallback, fallback);
        // RPAREN
        read_token();
        if (current_token != RPAREN) {
            print_error("Compiler is expecting an RPAREN.", token_buf, "");
        }
        // SEMICOLON
        read_token();
        if (current_token != SEMICOLON) {
            print_error("Compiler is expecting an SEMICOLON token.", token_buf, "");
        }
    }
    else {
        print_error("Unexpected character.", "Did you forget PARENTHESIS.", ";");
        move_forward();
    }
}

void condition(Opcode *oc, Operand or[/*2*/])
{
    TokenType t_tt;
    //char t_tb[MAX_TOKEN_LEN];

    or[0] = expression();
    read_token();
    t_tt = current_token;
    if (isRELATION_OP(t_tt)) {
        *oc = tt2oc(t_tt);
        or[1] = expression();
        // RPAREN
    }
}

Operand expression()
{
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    int exp_sign = 1;
    Bool first_sign = TRUE;

    Operand op1 = fallback;
    Operand op2;
    Operand op3;

    do {
        read_token();
        if (isADDITIVE_OP(current_token) || first_sign == TRUE) {
            first_sign = FALSE;
            switch (current_token) {
                case PLUS:
                    exp_sign = 1;
                    read_token();
                    break;
                case MINUS:
                    exp_sign = -1;
                    read_token();
                    break;
                default:
                    break;
            }
        }
        else {
            put_back();
            break;
        }
        t_tt = current_token;
        strcpy(t_tb, token_buf);
        if (t_tt == IDENT || t_tt == LPAREN || t_tt == C_INT || t_tt == C_FLOAT || t_tt == C_CHAR) {
            op2 = term(t_tt, t_tb);
            set_operand(&op3, enter_temp_element(), NORMAL_CONT);
            switch (exp_sign) {
                case -1:
                    enter_midcode(midcode_table_p, MINUS_OP, op1, op2, op3);
                    break;
                case 1:
                    enter_midcode(midcode_table_p, ADD_OP, op1, op2, op3);
                    break;
                default:
                    break;
            }
            op1 = op3;
        }
        else if (t_tt == RPAREN || t_tt == SEMICOLON || t_tt == COMMA || t_tt == C_STR) {
            put_back();
            break;
        }
        else {
            print_error("Error encountered while trying to indentify a factor: Expected token didn't appear.", t_tb, ";");
            return fallback;
            break;
        }
    } while(TRUE);
    // Check RPAREN, SEMICOLON, COMMA
    return op3;
}

Operand term(TokenType tt, char *tb)
{
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    TokenType t_op = ERROR;
    Operand op1;
    Operand op2;
    Operand op3;
    Bool in_term = FALSE;
    t_tt = tt;
    strcpy(t_tb, tb);

    // factor
    op1 = factor(t_tt, t_tb);
    // MULTIPLI_OP & factor
    read_token();
    while (isMULTIPLI_OP(current_token) == TRUE) {
        in_term = TRUE;
        t_op = current_token;
        read_token();
        t_tt = current_token;
        strcpy(t_tb, token_buf);

        op2 = factor(t_tt, t_tb);
        set_operand(&op3, enter_temp_element(), NORMAL_CONT);
        switch (t_op) {
            case TIMES:
                enter_midcode(midcode_table_p, MUL_OP, op1, op2, op3);
                break;
            case SLASH:
                enter_midcode(midcode_table_p, DIV_OP, op1, op2, op3);
                break;
            default:
                break;
        }
        op1 = op3;
        read_token();
    }
    if (in_term == FALSE) {
        op3 = op1;
    }
    put_back();
    return op3;
}

Operand factor(TokenType tt, char *tb)
{
    TokenType t_tt;
    char t_tb[MAX_TOKEN_LEN];
    FuncElement *t_fe;
    NormalElement *t_ne;
    Value value;
    // Midcode
    Operand op;
    t_tt = tt;
    strcpy(t_tb, tb);
    if (t_tt == MINUS) {
        t_tb[0] = '-';
        read_token();
        t_tt = current_token;
        strcpy(&t_tb[1], token_buf);
    }
    switch (t_tt) {
        case IDENT:
            // is function?
            t_fe = func_table_lookup(t_tb);
            if (t_fe != NULL) {
                if (t_fe->type == T_VOID) {
                    print_error("Function call within a factor must return a value.", token_buf, ";");
                    return fallback;
                } else {
                    op = func_ret_call(t_fe);
                }
                break;
            }
            // is variable or constant
            t_ne = normal_table_lookup(t_tb);
            if (t_ne != NULL) {
                set_operand(&op, t_ne, NORMAL_CONT);
                break;
            }
            print_error("Unrecognized identifier, it is neither a function nor a variable.", t_tb, ";");
            return fallback;
        case LPAREN:
            op = expression();
            read_token();
            if (current_token != RPAREN) {
                print_error("Compiler is expecting an RPAREN.", token_buf, ");");
                return fallback;
            }
            break;
        case C_INT:
        case C_FLOAT:
        case C_CHAR:
            value = str_to_value(t_tt, t_tb);
            set_const_operand(&op, &value, t_tt);
            break;
        default:
            break;
    }
    return op;
}